/* Copyright (c) 2010, Carl Burch. License information is located in the
 * com.cburch.logisim.Main source code and at www.cburch.com/logisim/. */

package com.cburch.logisim.circuit;

import java.awt.Color;
import java.awt.Graphics;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;

import org.apache.commons.collections15.iterators.IteratorChain;

import com.cburch.logisim.comp.Component;
import com.cburch.logisim.comp.ComponentDrawContext;
import com.cburch.logisim.comp.EndData;
import com.cburch.logisim.data.Attribute;
import com.cburch.logisim.data.AttributeEvent;
import com.cburch.logisim.data.AttributeListener;
import com.cburch.logisim.data.BitWidth;
import com.cburch.logisim.data.Bounds;
import com.cburch.logisim.data.Location;
import com.cburch.logisim.data.Value;
import com.cburch.logisim.instance.Instance;
import com.cburch.logisim.instance.StdAttr;
import com.cburch.logisim.std.wiring.PullResistor;
import com.cburch.logisim.std.wiring.Tunnel;
import com.cburch.logisim.util.GraphicsUtil;

class CircuitWires {
    static class SplitterData {
        // PointData associated with each end
        WireBundle[] end_bundle;

        SplitterData(int fan_out) {
            end_bundle = new WireBundle[fan_out + 1];
        }
    }

    static class ThreadBundle {
        int loc;
        WireBundle b;
        ThreadBundle(int loc, WireBundle b) {
            this.loc = loc;
            this.b = b;
        }
    }

    static class State {
        BundleMap bundleMap;
        HashMap<WireThread,Value> thr_values = new HashMap<WireThread,Value>();

        State(BundleMap bundleMap) {
            this.bundleMap = bundleMap;
        }

        @Override
        public Object clone() {
            State ret = new State(this.bundleMap);
            ret.thr_values.putAll(this.thr_values);
            return ret;
        }
    }

    private class TunnelListener implements AttributeListener {
        @Override
        public void attributeListChanged(AttributeEvent e) { }

        @Override
        public void attributeValueChanged(AttributeEvent e) {
            Attribute<?> attr = e.getAttribute();
            if (attr == StdAttr.LABEL || attr == PullResistor.ATTR_PULL_TYPE) {
                voidBundleMap();
            }
        }
    }

    static class BundleMap {
        boolean computed = false;
        HashMap<Location,WireBundle> pointBundles = new HashMap<Location,WireBundle>();
        HashSet<WireBundle> bundles = new HashSet<WireBundle>();
        boolean isValid = true;
        // NOTE: It would make things more efficient if we also had
        // a set of just the first bundle in each tree.
        HashSet<WidthIncompatibilityData> incompatibilityData = null;

        HashSet<WidthIncompatibilityData> getWidthIncompatibilityData() {
            return incompatibilityData;
        }

        void addWidthIncompatibilityData(WidthIncompatibilityData e) {
            if (incompatibilityData == null) {
                incompatibilityData = new HashSet<WidthIncompatibilityData>();
            }
            incompatibilityData.add(e);
        }

        WireBundle getBundleAt(Location p) {
            return pointBundles.get(p);
        }

        WireBundle createBundleAt(Location p) {
            WireBundle ret = pointBundles.get(p);
            if (ret == null) {
                ret = new WireBundle();
                pointBundles.put(p, ret);
                ret.points.add(p);
                bundles.add(ret);
            }
            return ret;
        }

        boolean isValid() {
            return isValid;
        }

        void invalidate() {
            isValid = false;
        }

        void setBundleAt(Location p, WireBundle b) {
            pointBundles.put(p, b);
        }

        Set<Location> getBundlePoints() {
            return pointBundles.keySet();
        }

        Set<WireBundle> getBundles() {
            return bundles;
        }

        synchronized void markComputed() {
            computed = true;
            notifyAll();
        }

        synchronized void waitUntilComputed() {
            while (!computed) {
                try { wait(); } catch (InterruptedException e) { }
            }
        }
    }

    // user-given data
    private HashSet<Wire> wires = new HashSet<Wire>();
    private HashSet<Splitter> splitters = new HashSet<Splitter>();
    // of Components with Tunnel factory
    private HashSet<Component> tunnels = new HashSet<Component>();
    private TunnelListener tunnelListener = new TunnelListener();
    // of Components with PullResistor factory
    private HashSet<Component> pulls = new HashSet<Component>();
    final CircuitPoints points = new CircuitPoints();

    // derived data
    private Bounds bounds = Bounds.EMPTY_BOUNDS;
    private BundleMap bundleMap = null;

    CircuitWires() { }

    //
    // query methods
    //
    boolean isMapVoided() {
        return bundleMap == null;
    }

    Set<WidthIncompatibilityData> getWidthIncompatibilityData() {
        return getBundleMap().getWidthIncompatibilityData();
    }

    void ensureComputed() {
        getBundleMap();
    }

    BitWidth getWidth(Location q) {
        BitWidth det = points.getWidth(q);
        if (det != BitWidth.UNKNOWN) {
            return det;
        }


        BundleMap bmap = getBundleMap();
        if (!bmap.isValid()) {
            return BitWidth.UNKNOWN;
        }

        WireBundle qb = bmap.getBundleAt(q);
        if (qb != null && qb.isValid()) {
            return qb.getWidth();
        }


        return BitWidth.UNKNOWN;
    }

    Location getWidthDeterminant(Location q) {
        BitWidth det = points.getWidth(q);
        if (det != BitWidth.UNKNOWN) {
            return q;
        }


        WireBundle qb = getBundleMap().getBundleAt(q);
        if (qb != null && qb.isValid()) {
            return qb.getWidthDeterminant();
        }


        return q;
    }

    Iterator<? extends Component> getComponents() {
        return new IteratorChain<Component>(splitters.iterator(),
                wires.iterator());
    }

    Set<Wire> getWires() {
        return wires;
    }

    Bounds getWireBounds() {
        Bounds bds = bounds;
        if (bds == Bounds.EMPTY_BOUNDS) {
            bds = recomputeBounds();
        }
        return bds;
    }

    WireBundle getWireBundle(Location query) {
        BundleMap bmap = getBundleMap();
        return bmap.getBundleAt(query);
    }

    WireSet getWireSet(Wire start) {
        WireBundle bundle = getWireBundle(start.e0);
        if (bundle == null) {
            return WireSet.EMPTY;
        }

        HashSet<Wire> wires = new HashSet<Wire>();
        for (Location loc : bundle.points) {
            wires.addAll(points.getWires(loc));
        }
        return new WireSet(wires);
    }

    //
    // action methods
    //
    // NOTE: this could be made much more efficient in most cases to
    // avoid voiding the bundle map.
    boolean add(Component comp) {
        boolean added = true;
        if (comp instanceof Wire) {
            added = addWire((Wire) comp);
        } else if (comp instanceof Splitter) {
            splitters.add((Splitter) comp);
        } else {
            Object factory = comp.getFactory();
            if (factory instanceof Tunnel) {
                tunnels.add(comp);
                comp.getAttributeSet().addAttributeListener(tunnelListener);
            } else if (factory instanceof PullResistor) {
                pulls.add(comp);
                comp.getAttributeSet().addAttributeListener(tunnelListener);
            }
        }
        if (added) {
            points.add(comp);
            voidBundleMap();
        }
        return added;
    }

    void remove(Component comp) {
        if (comp instanceof Wire) {
            removeWire((Wire) comp);
        } else if (comp instanceof Splitter) {
            splitters.remove(comp);
        } else {
            Object factory = comp.getFactory();
            if (factory instanceof Tunnel) {
                tunnels.remove(comp);
                comp.getAttributeSet().removeAttributeListener(tunnelListener);
            } else if (factory instanceof PullResistor) {
                pulls.remove(comp);
                comp.getAttributeSet().removeAttributeListener(tunnelListener);
            }
        }
        points.remove(comp);
        voidBundleMap();
    }

    void add(Component comp, EndData end) {
        points.add(comp, end);
        voidBundleMap();
    }

    void remove(Component comp, EndData end) {
        points.remove(comp, end);
        voidBundleMap();
    }

    void replace(Component comp, EndData oldEnd, EndData newEnd) {
        points.remove(comp, oldEnd);
        points.add(comp, newEnd);
        voidBundleMap();
    }

    private boolean addWire(Wire w) {
        boolean added = wires.add(w);
        if (!added) {
            return false;
        }


        // update bounds
        if (bounds != Bounds.EMPTY_BOUNDS) {
            bounds = bounds.add(w.e0).add(w.e1);
        }
        return true;
    }

    private void removeWire(Wire w) {
        boolean removed = wires.remove(w);
        if (!removed) {
            return;
        }


        if (bounds != Bounds.EMPTY_BOUNDS) {
            // bounds is valid - invalidate if endpoint on border
            Bounds smaller = bounds.expand(-2);
            if (!smaller.contains(w.e0) || !smaller.contains(w.e1)) {
                bounds = Bounds.EMPTY_BOUNDS;
            }
        }
    }

    //
    // utility methods
    //
    void propagate(CircuitState circState, Set<Location> points) {
        BundleMap map = getBundleMap();
        // affected threads
        CopyOnWriteArraySet<WireThread> dirtyThreads = new CopyOnWriteArraySet<WireThread>();

        // get state, or create a new one if current state is outdated
        State s = circState.getWireData();
        if (s == null || s.bundleMap != map) {
            // if it is outdated, we need to compute for all threads
            s = new State(map);
            for (WireBundle b : map.getBundles()) {
                WireThread[] th = b.threads;
                if (b.isValid() && th != null) {
                    for (WireThread t : th) {
                        dirtyThreads.add(t);
                    }
                }
            }
            circState.setWireData(s);
        }

        // determine affected threads, and set values for unwired points
        for (Location p : points) {
            WireBundle pb = map.getBundleAt(p);
            // point is not wired
            if (pb == null) {
                circState.setValueByWire(p, circState.getComponentOutputAt(p));
            } else {
                WireThread[] th = pb.threads;
                if (!pb.isValid() || th == null) {
                    // immediately propagate NILs across invalid bundles
                    CopyOnWriteArraySet<Location> pbPoints = pb.points;
                    if (pbPoints == null) {
                        circState.setValueByWire(p, Value.NIL);
                    } else {
                        for (Location loc2 : pbPoints) {
                            circState.setValueByWire(loc2, Value.NIL);
                        }
                    }
                } else {
                    for (WireThread t : th) {
                        dirtyThreads.add(t);
                    }
                }
            }
        }

        if (dirtyThreads.isEmpty()) {
            return;
        }


        // determine values of affected threads
        HashSet<ThreadBundle> bundles = new HashSet<ThreadBundle>();
        for (WireThread t : dirtyThreads) {
            Value v = getThreadValue(circState, t);
            s.thr_values.put(t, v);
            bundles.addAll(t.getBundles());
        }

        // now propagate values through circuit
        for (ThreadBundle tb : bundles) {
            WireBundle b = tb.b;

            Value bv = null;
            if (!b.isValid() || b.threads == null) {
                // do nothing
                ;
            } else if (b.threads.length == 1) {
                bv = s.thr_values.get(b.threads[0]);
            } else {
                Value[] tvs = new Value[b.threads.length];
                boolean tvs_valid = true;
                for (int i = 0; i < tvs.length; i++) {
                    Value tv = s.thr_values.get(b.threads[i]);
                    if (tv == null) {
                        { tvs_valid = false;
                    }
 break; }
                    tvs[i] = tv;
                }
                if (tvs_valid) {
                    bv = Value.create(tvs);
                }

            }

            if (bv != null) {
                for (Location p : b.points) {
                    circState.setValueByWire(p, bv);
                }
            }
        }
    }

    void draw(ComponentDrawContext context, Collection<Component> hidden) {
        boolean showState = context.getShowState();
        CircuitState state = context.getCircuitState();
        Graphics g = context.getGraphics();
        g.setColor(Color.BLACK);
        GraphicsUtil.switchToWidth(g, Wire.WIDTH);
        WireSet highlighted = context.getHighlightedWires();

        BundleMap bmap = getBundleMap();
        boolean isValid = bmap.isValid();
        if (hidden == null || hidden.size() == 0) {
            for (Wire w : wires) {
                Location s = w.e0;
                Location t = w.e1;
                WireBundle wb = bmap.getBundleAt(s);
                if (!wb.isValid()) {
                    g.setColor(Value.WIDTH_ERROR_COLOR);
                } else if (showState) {
                    if (!isValid) {
                        g.setColor(Value.NIL_COLOR);
                    }

                    else {
                                g.setColor(state.getValue(s).getColor());
                    }

                } else {
                    g.setColor(Color.BLACK);
                }
                if (highlighted.containsWire(w)) {
                    GraphicsUtil.switchToWidth(g, Wire.WIDTH + 2);
                    g.drawLine(s.getX(), s.getY(), t.getX(), t.getY());
                    GraphicsUtil.switchToWidth(g, Wire.WIDTH);
                } else {
                    g.drawLine(s.getX(), s.getY(), t.getX(), t.getY());
                }
            }

            for (Location loc : points.getSplitLocations()) {
                if (points.getComponentCount(loc) > 2) {
                    WireBundle wb = bmap.getBundleAt(loc);
                    if (wb != null) {
                        if (!wb.isValid()) {
                            g.setColor(Value.WIDTH_ERROR_COLOR);
                        } else if (showState) {
                            if (!isValid) {
                                g.setColor(Value.NIL_COLOR);
                            }

                            else {
                                        g.setColor(state.getValue(loc).getColor());
                            }

                        } else {
                            g.setColor(Color.BLACK);
                        }
                        if (highlighted.containsLocation(loc)) {
                            g.fillOval(loc.getX() - 5, loc.getY() - 5, 10, 10);
                        } else {
                            g.fillOval(loc.getX() - 4, loc.getY() - 4, 8, 8);
                        }
                    }
                }
            }
        } else {
            for (Wire w : wires) {
                if (!hidden.contains(w)) {
                    Location s = w.e0;
                    Location t = w.e1;
                    WireBundle wb = bmap.getBundleAt(s);
                    if (!wb.isValid()) {
                        g.setColor(Value.WIDTH_ERROR_COLOR);
                    } else if (showState) {
                        if (!isValid) {
                            g.setColor(Value.NIL_COLOR);
                        }

                        else {
                                    g.setColor(state.getValue(s).getColor());
                        }

                    } else {
                        g.setColor(Color.BLACK);
                    }
                    if (highlighted.containsWire(w)) {
                        GraphicsUtil.switchToWidth(g, Wire.WIDTH + 2);
                        g.drawLine(s.getX(), s.getY(), t.getX(), t.getY());
                        GraphicsUtil.switchToWidth(g, Wire.WIDTH);
                    } else {
                        g.drawLine(s.getX(), s.getY(), t.getX(), t.getY());
                    }
                }
            }

            // this is just an approximation, but it's good enough since
            // the problem is minor, and hidden only exists for a short
            // while at a time anway.
            for (Location loc : points.getSplitLocations()) {
                if (points.getComponentCount(loc) > 2) {
                    int icount = 0;
                    for (Component comp : points.getComponents(loc)) {
                        if (!hidden.contains(comp)) {
                            ++icount;
                        }

                    }
                    if (icount > 2) {
                        WireBundle wb = bmap.getBundleAt(loc);
                        if (wb != null) {
                            if (!wb.isValid()) {
                                g.setColor(Value.WIDTH_ERROR_COLOR);
                            } else if (showState) {
                                if (!isValid) {
                                    g.setColor(Value.NIL_COLOR);
                                }

                                else {
                                            g.setColor(state.getValue(loc).getColor());
                                }

                            } else {
                                g.setColor(Color.BLACK);
                            }
                            if (highlighted.containsLocation(loc)) {
                                g.fillOval(loc.getX() - 5, loc.getY() - 5, 10, 10);
                            } else {
                                g.fillOval(loc.getX() - 4, loc.getY() - 4, 8, 8);
                            }
                        }
                    }
                }
            }
        }
    }

    //
    // helper methods
    //
    private void voidBundleMap() {
        bundleMap = null;
    }

    private BundleMap getBundleMap() {
        // Maybe we already have a valid bundle map (or maybe
        // one is in progress).
        BundleMap ret = bundleMap;
        if (ret != null) {
            ret.waitUntilComputed();
            return ret;
        }
        try {
            // Ok, we have to create our own.
            for (int tries = 4; tries >= 0; tries--) {
                try {
                    ret = new BundleMap();
                    computeBundleMap(ret);
                    bundleMap = ret;
                    break;
        } catch (Exception e) {
                    if (tries == 0) {
                        e.printStackTrace();
                        System.err.println( e.getLocalizedMessage() );
                        bundleMap = ret;
                    }
                }
            }
        } catch (RuntimeException ex) {
            ret.invalidate();
            ret.markComputed();
            throw ex;
        } finally {
            // Mark the BundleMap as computed in case anybody is waiting for the result.
            ret.markComputed();
        }
        return ret;
    }

    // To be called by getBundleMap only
    private void computeBundleMap(BundleMap ret) {
        // create bundles corresponding to wires and tunnels
        connectWires(ret);
        connectTunnels(ret);
        connectPullResistors(ret);

        // merge any WireBundle objects united by previous steps
        for (Iterator<WireBundle> it = ret.getBundles().iterator(); it.hasNext(); ) {
            WireBundle b = it.next();
            WireBundle bpar = b.find();
            // b isn't group's representative
            if (bpar != b) {
                for (Location pt : b.points) {
                    ret.setBundleAt(pt, bpar);
                    bpar.points.add(pt);
                }
                bpar.addPullValue(b.getPullValue());
                it.remove();
            }
        }

        // make a WireBundle object for each end of a splitter
        for (Splitter spl : splitters) {
            List<EndData> ends = new ArrayList<EndData>(spl.getEnds());
            for (EndData end : ends) {
                Location p = end.getLocation();
                WireBundle pb = ret.createBundleAt(p);
                pb.setWidth(end.getWidth(), p);
            }
        }

        // set the width for each bundle whose size is known
        // based on components
        for (Location p : ret.getBundlePoints()) {
            WireBundle pb = ret.getBundleAt(p);
            BitWidth width = points.getWidth(p);
            if (width != BitWidth.UNKNOWN) {
                pb.setWidth(width, p);
            }
        }

        // determine the bundles at the end of each splitter
        for (Splitter spl : splitters) {
            List<EndData> ends = new ArrayList<EndData>(spl.getEnds());
            int index = -1;
            for (EndData end : ends) {
                index++;
                Location p = end.getLocation();
                WireBundle pb = ret.getBundleAt(p);
                if (pb != null) {
                    pb.setWidth(end.getWidth(), p);
                    spl.wire_data.end_bundle[index] = pb;
                }
            }
        }

        // unite threads going through splitters
        for (Splitter spl : splitters) {
            synchronized(spl) {
                SplitterAttributes spl_attrs = (SplitterAttributes) spl.getAttributeSet();
                byte[] bit_end = spl_attrs.bit_end;
                SplitterData spl_data = spl.wire_data;
                WireBundle from_bundle = spl_data.end_bundle[0];
                if (from_bundle == null || !from_bundle.isValid()) {
                    continue;
                }


                for (int i = 0; i < bit_end.length; i++) {
                    int j = bit_end[i];
                    if (j > 0) {
                        int thr = spl.bit_thread[i];
                        WireBundle to_bundle = spl_data.end_bundle[j];
                        WireThread[] to_threads = to_bundle.threads;
                        if (to_threads != null && to_bundle.isValid()) {
                            WireThread[] from_threads = from_bundle.threads;
                            if (i >= from_threads.length) {
                                throw new ArrayIndexOutOfBoundsException("from " + i + " of " + from_threads.length);
                            }
                            if (thr >= to_threads.length) {
                                throw new ArrayIndexOutOfBoundsException("to " + thr + " of " + to_threads.length);
                            }
                            from_threads[i].unite(to_threads[thr]);
                        }
                    }
                }
            }
        }

        // merge any threads united by previous step
        for (WireBundle b : ret.getBundles()) {
            if (b.isValid() && b.threads != null) {
                for (int i = 0; i < b.threads.length; i++) {
                    WireThread thr = b.threads[i].find();
                    b.threads[i] = thr;
                    thr.getBundles().add(new ThreadBundle(i, b));
                }
            }
        }

        // All threads are sewn together! Compute the exception set before leaving
        Collection<WidthIncompatibilityData> exceptions = points.getWidthIncompatibilityData();
        if (exceptions != null && exceptions.size() > 0) {
            for (WidthIncompatibilityData wid : exceptions) {
                ret.addWidthIncompatibilityData(wid);
            }
        }
        for (WireBundle b : ret.getBundles()) {
            WidthIncompatibilityData e = b.getWidthIncompatibilityData();
            if (e != null) {
                ret.addWidthIncompatibilityData(e);
            }

        }
    }

    private void connectWires(BundleMap ret) {
        // make a WireBundle object for each tree of connected wires
        for (Wire w : wires) {
            WireBundle b0 = ret.getBundleAt(w.e0);
            if (b0 == null) {
                WireBundle b1 = ret.createBundleAt(w.e1);
                b1.points.add(w.e0); ret.setBundleAt(w.e0, b1);
            } else {
                WireBundle b1 = ret.getBundleAt(w.e1);
                // t1 doesn't exist
                if (b1 == null) {
                    b0.points.add(w.e1); ret.setBundleAt(w.e1, b0);
                } else {
                    // unite b0 and b1
                    b1.unite(b0);
                }
            }
        }
    }

    private void connectTunnels(BundleMap ret) {
        // determine the sets of tunnels
        HashMap<String,ArrayList<Location>> tunnelSets = new HashMap<String,ArrayList<Location>>();
        for (Component comp : tunnels) {
            String label = comp.getAttributeSet().getValue(StdAttr.LABEL);
            label = label.trim();
            if (!label.equals("")) {
                ArrayList<Location> tunnelSet = tunnelSets.get(label);
                if (tunnelSet == null) {
                    tunnelSet = new ArrayList<Location>(3);
                    tunnelSets.put(label, tunnelSet);
                }
                tunnelSet.add(comp.getLocation());
            }
        }

        // now connect the bundles that are tunnelled together
        for (ArrayList<Location> tunnelSet : tunnelSets.values()) {
            WireBundle foundBundle = null;
            Location foundLocation = null;
            for (Location loc : tunnelSet) {
                WireBundle b = ret.getBundleAt(loc);
                if (b != null) {
                    foundBundle = b;
                    foundLocation = loc;
                    break;
                }
            }
            if (foundBundle == null) {
                foundLocation = tunnelSet.get(0);
                foundBundle = ret.createBundleAt(foundLocation);
            }
            for (Location loc : tunnelSet) {
                if (loc != foundLocation) {
                    WireBundle b = ret.getBundleAt(loc);
                    if (b == null) {
                        foundBundle.points.add(loc);
                        ret.setBundleAt(loc, foundBundle);
                    } else {
                        b.unite(foundBundle);
                    }
                }
            }
        }
    }

    private void connectPullResistors(BundleMap ret) {
        for (Component comp : pulls) {
            Location loc = comp.getEnd(0).getLocation();
            WireBundle b = ret.getBundleAt(loc);
            if (b == null) {
                b = ret.createBundleAt(loc);
                b.points.add(loc);
                ret.setBundleAt(loc, b);
            }
            Instance instance = Instance.getInstanceFor(comp);
            b.addPullValue(PullResistor.getPullValue(instance));
        }
    }

    private Value getThreadValue(CircuitState state, WireThread t) {
        Value ret = Value.UNKNOWN;
        Value pull = Value.UNKNOWN;
        for (ThreadBundle tb : t.getBundles()) {
            for (Location p : tb.b.points) {
                Value val = state.getComponentOutputAt(p);
                if (val != null && val != Value.NIL) {
                    ret = ret.combine(val.get(tb.loc));
                }
            }
            Value pullHere = tb.b.getPullValue();
            if (pullHere != Value.UNKNOWN) {
                pull = pull.combine(pullHere);
            }

        }
        if (pull != Value.UNKNOWN) {
            ret = pullValue(ret, pull);
        }
        return ret;
    }

    private static Value pullValue(Value base, Value pullTo) {
        if (base.isFullyDefined()) {
            return base;
        } else if (base.getWidth() == 1) {
            if (base == Value.UNKNOWN) {
                return pullTo;
            }

            else {
                return base;
            }

        } else {
            Value[] ret = base.getAll();
            for (int i = 0; i < ret.length; i++) {
                if (ret[i] == Value.UNKNOWN) {
                    ret[i] = pullTo;
                }

            }
            return Value.create(ret);
        }
    }

    private Bounds recomputeBounds() {
        Iterator<Wire> it = wires.iterator();
        if (!it.hasNext()) {
            bounds = Bounds.EMPTY_BOUNDS;
            return Bounds.EMPTY_BOUNDS;
        }

        Wire w = it.next();
        int xmin = w.e0.getX();
        int ymin = w.e0.getY();
        int xmax = w.e1.getX();
        int ymax = w.e1.getY();
        while (it.hasNext()) {
            w = it.next();
            int x0 = w.e0.getX(); if (x0 < xmin) xmin = x0;
            int x1 = w.e1.getX(); if (x1 > xmax) xmax = x1;
            int y0 = w.e0.getY(); if (y0 < ymin) ymin = y0;
            int y1 = w.e1.getY(); if (y1 > ymax) ymax = y1;
        }
        Bounds bds = Bounds.create(xmin, ymin,
            xmax - xmin + 1, ymax - ymin + 1);
        bounds = bds;
        return bds;
    }
}
